#include "lv_drv_conf.h"

#if USE_CRANE_JPU

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include "../../board.h"
#include "../common/utils.h"
#include "lv_drivers/display/st7789v_spi.h"
#include "lv_drivers/display/scaler.h"
#include "crane_jpuc_decoder.h"
#include "crane_jpuc_common.h"

#define JPEG_HEADER_MARKER_DQT    0xdb
#define JPEG_HEADER_MARKER_SOS    0xda
#define JPEG_HEADER_MARKER_DHT    0xc4
#define JPEG_HEADER_MARKER_SOF0   0xc0
#define JPEG_HEADER_DHT_DC_LUM    0x00
#define JPEG_HEADER_DHT_DC_CHROM  0x01
#define JPEG_HEADER_DHT_AC_LUM    0x10
#define JPEG_HEADER_DHT_AC_CHROM  0x11

#define CRANE_ENDIAN_VALUE   0

#ifndef UOS_FLAG_OR_CLEAR
#define UOS_FLAG_OR_CLEAR       8
#endif

const uint8_t jpeg_default_huff_dc_lum[JPEG_HUFF_TABLE_DC_SIZE] = {
    0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b};
const uint8_t jpeg_default_huff_dc_chrom[JPEG_HUFF_TABLE_DC_SIZE] = {
    0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b};
const uint8_t jpeg_default_huff_ac_lum[JPEG_HUFF_TABLE_AC_SIZE] = {
    0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d,
    0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
    0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
    0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
    0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
    0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
    0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
    0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
    0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
    0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
    0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
    0xf9, 0xfa};
const uint8_t jpeg_default_huff_ac_chrom[JPEG_HUFF_TABLE_AC_SIZE] = {
    0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77,
    0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
    0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
    0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
    0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
    0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
    0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
    0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
    0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
    0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
    0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
    0xf9, 0xfa};

typedef struct jpu_decoder_info_s {
    uint32_t src_addr;          //stream address
    uint32_t buffer_size;       //stream size
    uint32_t page_ptr;
    uint32_t word_ptr;
    uint32_t read_ptr;
    uint32_t yuv_format;
    uint32_t rst_interval;
    uint32_t huff_dc_idx;
    uint32_t huff_ac_idx;
    uint32_t out_y_addr;
    uint32_t out_u_addr;
    uint32_t out_v_addr;
    uint16_t huff_table_min[64];
    uint16_t huff_table_max[64];
    uint8_t huff_table_ptr[64];
    jpeg_header_info_t header_info;
} jpu_decoder_info_t;

static uint8_t jpu_decode_prepare(uint8_t *img_data, uint32_t data_size, jpu_decoder_info_t *jpeg_info);
static void jpu_decode_init(void);
static uint32_t jpu_decode_deinit(void);
static void jpu_decode_bbc_ready(uint32_t page_ptr, uint32_t src_buffer);
static void jpu_decode_seq_init(jpu_decoder_info_t *jpeg_info);
static void jpu_decode_dqt_set(jpu_decoder_info_t *jpeg_info);
static void jpu_decode_dht_set(jpu_decoder_info_t *jpeg_info);
static uint8_t jpu_decode_run(jpu_decoder_info_t *jpeg_info);

static jpu_decode_config_t jpu_decode_config;

extern uint8_t uos_create_flag (void);
extern void  uos_delete_flag(uint8_t id);
extern int  uos_wait_flag (uint8_t id, uint32_t mask, uint32_t op, uint32_t *flags, uint32_t timeout);

uint8_t jpeg_header_parser(uint8_t *data, uint32_t data_length, jpeg_header_info_t *header_info)
{
    /*
    1. ptr     v1
    2. width   v1
    3. height  v1
    4. huff table  v1
    5. huff table index : ac / dc  x
    6. Qtable  0xFFDB  v1
    */
    if(data == NULL || header_info == NULL) {
        printf("Error :please check jpeg_header_parser param!!\n");
        return CRANE_JPEG_DECODE_FAIL;
    }

    memset(header_info, 0, sizeof(jpeg_header_info_t));

    uint32_t offset = 2;  //skip oxFFD8
    uint16_t marker_length;
    jpeg_header_info_t jpeg_header_info;
    memset(&jpeg_header_info, 0, sizeof(jpeg_header_info_t));

    while(offset < data_length-1) {
        while(data[offset] != 0xff) {
            offset ++;
            if(offset >= (data_length-1))
                return CRANE_JPEG_DECODE_FAIL;
        }

        uint8_t marker = data[++offset];
        if(offset>=(data_length-1)) {
            return CRANE_JPEG_DECODE_FAIL;
        }
        marker_length = (data[offset + 1]<<8) + data[offset + 2];
        //printf("jpeg_header_parser: marker: %x, offset: %x, length: %d\n", marker, offset, marker_length);
        switch(marker)
        {
            case JPEG_HEADER_MARKER_SOS:
            {
                uint32_t sos_end = offset + marker_length + 1;
                if(sos_end < data_length) {
                    jpeg_header_info.sos_end_position = offset + marker_length + 1;
                    //printf("jpeg sos_end_position: 0x%x\n", jpeg_header_info.sos_end_position);
                    if(jpeg_header_info.width != 0
                        && jpeg_header_info.height != 0
                        && jpeg_header_info.qtable_got != 0) {
                        memcpy(header_info, &jpeg_header_info, sizeof(jpeg_header_info_t));
                        return CRANE_JPEG_DECODE_PASS;
                    } else {
                        return CRANE_JPEG_DECODE_FAIL;
                    }
                } else {
                    return CRANE_JPEG_DECODE_FAIL;
                }
            }

            case JPEG_HEADER_MARKER_SOF0:
                if((offset + marker_length) < data_length) {
                    jpeg_header_info.width = (data[offset+6]<<8) + data[offset+7];
                    jpeg_header_info.height = (data[offset+4]<<8) + data[offset+5];
                    //printf("jpeg size: 0x%x, 0x%x\n", jpeg_header_info.width, jpeg_header_info.height);
                } else {
                    return CRANE_JPEG_DECODE_FAIL;
                }
                break;

            case JPEG_HEADER_MARKER_DQT:
                if((offset + marker_length) < data_length) {
                    if(marker_length > 0x43){
                        printf("Warning:  jpeg DQT size is 0x%x !!!!\n", marker_length);
                        return CRANE_JPEG_DECODE_FAIL;
                    }
                    int q_index = data[offset+3];
                    if((q_index&0xf) >= 4){
                        printf("Err: jpeg_header_parser: qindex[0x%x] is error!!!\n", q_index);
                        return CRANE_JPEG_DECODE_FAIL;
                    }
                    jpeg_header_info.qtable_got |=  (1 << (q_index&0xf));
                    //printf("jpeg q_index: %d, %d\n", (uint8_t)q_index, jpeg_header_info.qtable_got);
                    memcpy(&(jpeg_header_info.qtable[q_index&0xf][0]), &(data[offset + 4]), marker_length-3);
                } else {
                    return CRANE_JPEG_DECODE_FAIL;
                }
                break;

            case JPEG_HEADER_MARKER_DHT:
                if((offset + marker_length) < data_length) {
                    int huff_idx = data[offset+3];
                    if(((huff_idx&0xF0) && (marker_length > 0xB5))
                        || (((huff_idx&0xF0)==0) && (marker_length > 0x1F))){
                        printf("Error:  jpeg HUFF[0x%x] size is 0x%x !!!!\n", huff_idx, marker_length);
                        return CRANE_JPEG_DECODE_FAIL;
                    }
                    if(huff_idx == JPEG_HEADER_DHT_DC_LUM){
                        memcpy(&(jpeg_header_info.huff_table_dc_lum[0]), &(data[offset + 4]), marker_length-3);
                        jpeg_header_info.huff_got |= BIT_0;
                        jpeg_header_info.huff_table_dc_lum_length = marker_length-3-JPEG_HUFF_TABLE_LENGTH_SIZE;
                    } else if(huff_idx == JPEG_HEADER_DHT_DC_CHROM){
                        memcpy(&(jpeg_header_info.huff_table_dc_chrom[0]), &(data[offset + 4]), marker_length-3);
                        jpeg_header_info.huff_got |= BIT_1;
                        jpeg_header_info.huff_table_dc_chrom_length = marker_length-3-JPEG_HUFF_TABLE_LENGTH_SIZE;
                    } else if(huff_idx == JPEG_HEADER_DHT_AC_LUM){
                        memcpy(&(jpeg_header_info.huff_table_ac_lum[0]), &(data[offset + 4]), marker_length-3);
                        jpeg_header_info.huff_got |= BIT_2;
                        jpeg_header_info.huff_table_ac_lum_length = marker_length-3-JPEG_HUFF_TABLE_LENGTH_SIZE;
                    } else if(huff_idx == JPEG_HEADER_DHT_AC_CHROM){
                        memcpy(&(jpeg_header_info.huff_table_ac_chrom[0]), &(data[offset + 4]), marker_length-3);
                        jpeg_header_info.huff_got |= BIT_3;
                        jpeg_header_info.huff_table_ac_chrom_length = marker_length-3-JPEG_HUFF_TABLE_LENGTH_SIZE;
                    }
                } else {
                    return CRANE_JPEG_DECODE_FAIL;
                }
                break;

            default:
                break;
        }

        offset += marker_length;
        //printf("jpeg_header_parser: marker_length: %x \n", marker_length);
        offset++;
    }

    return CRANE_JPEG_DECODE_FAIL;
}

uint8_t jpu_decode(uint8_t *jpeg_data, uint32_t data_size, jpeg_header_info_t *header_info,
                       uint8_t *dest_buf_yuv, uint8_t *dest_buf_rgb565, uint16_t dest_width, uint16_t dest_height)
{
    jpu_decoder_info_t jpeg_info;

    printf("\njpu_decode\n");
    if (header_info != NULL) {
        memcpy(&jpeg_info.header_info, header_info, sizeof(jpeg_header_info_t));
    } else {
        if(jpeg_header_parser(jpeg_data, data_size, &(jpeg_info.header_info)) == CRANE_JPEG_DECODE_FAIL) {
            printf("jpeg_header_parser failed!!!!!\n");
            return CRANE_JPEG_DECODE_FAIL;
        }
    }

    if (jpu_decode_prepare(jpeg_data, data_size, &jpeg_info) == CRANE_JPEG_DECODE_FAIL) {
        return CRANE_JPEG_DECODE_FAIL;
    }

    jpeg_info.out_y_addr = (uint32_t)dest_buf_yuv;
    jpeg_info.out_u_addr = (uint32_t)dest_buf_yuv + (jpeg_info.header_info.width * jpeg_info.header_info.height);

    jpu_decode_init();

    /* Init load bitstream */
    jpu_write(MJPEG_BBC_RD_PTR_REG, jpeg_info.src_addr);
    jpu_write(MJPEG_BBC_WR_PTR_REG, jpeg_info.src_addr);

    jpu_decode_bbc_ready(jpeg_info.page_ptr, jpeg_info.src_addr);
    jpu_decode_seq_init(&jpeg_info);
    jpu_decode_dqt_set(&jpeg_info);
    if (!jpeg_info.header_info.huff_is_default) {
        jpu_decode_dht_set(&jpeg_info);
    }

    if (jpu_decode_run(&jpeg_info) == CRANE_JPEG_DECODE_FAIL) {
        jpu_decode_deinit();
        return CRANE_JPEG_DECODE_FAIL;
    } else {
        jpu_decode_deinit();

        uint8_t *tmp_buf = dest_buf_rgb565;
        if((jpeg_info.header_info.width!=dest_width) || (jpeg_info.header_info.height!=dest_height)) {
            tmp_buf = lv_mem_aligned_alloc(jpeg_info.header_info.width * jpeg_info.header_info.height * 2, 8);
            if(tmp_buf == NULL){
                printf("jpu_decode malloc failed!!!!!\n");
                return CRANE_JPEG_DECODE_FAIL;
            }
        }
        yuv420_2_rgb565(jpeg_info.header_info.width,
                         jpeg_info.header_info.height,
                         dest_buf_yuv,
                         (unsigned short *)tmp_buf,
                         1);
        if((jpeg_info.header_info.width!=dest_width) || (jpeg_info.header_info.height!=dest_height)) {
            if(rgb565_scale(jpeg_info.header_info.width,
                             jpeg_info.header_info.height,
                             dest_width,
                             dest_height,
                             tmp_buf,
                             dest_buf_rgb565) == 0) {
                lv_mem_aligned_free(tmp_buf);
                return CRANE_JPEG_DECODE_FAIL;
            }
            lv_mem_aligned_free(tmp_buf);
        }
        return CRANE_JPEG_DECODE_PASS;
    }
}

static uint8_t jpu_decode_prepare(uint8_t *img_data, uint32_t data_size, jpu_decoder_info_t *jpeg_info)
{
    uint32_t ptr = jpeg_info->header_info.sos_end_position;
    uint32_t page_ptr, word_ptr, read_ptr;

    jpeg_info->src_addr = (uint32_t)img_data;
    jpeg_info->buffer_size = data_size;
    page_ptr = ptr / 256;         //page pointer for BBC

    word_ptr = (ptr % 256) / 4;   //word uint(4 byte), word pointer for GBU
    if (page_ptr & 1)
        word_ptr += 64;           // offset of second bitstream buffer of GBU
    if (word_ptr & 1)
        word_ptr -= 1;            // to make even.

    read_ptr = (ptr % 4) * 8;     // read pointer for GBU
    if (((ptr % 256) / 4) & 1)
        read_ptr += 32;

    //printf("jpeg_decode_header: 0x%x, 0x%x, 0x%x, 0x%x\n", ptr, page_ptr, word_ptr, read_ptr);

    jpeg_info->page_ptr = page_ptr;
    jpeg_info->read_ptr = read_ptr;
    jpeg_info->word_ptr = word_ptr;

    jpeg_info->yuv_format = 0x00;        //support NV12 only yet.
    jpeg_info->rst_interval = 0x0000;
    jpeg_info->huff_dc_idx = 0x03;
    jpeg_info->huff_ac_idx = 0x03;

    jpeg_info->header_info.huff_is_default = 1;

    if (jpeg_info->header_info.huff_got) {
        if ((memcmp(jpeg_info->header_info.huff_table_dc_lum, jpeg_default_huff_dc_lum, JPEG_HUFF_TABLE_DC_SIZE)!=0)
            || (memcmp(jpeg_info->header_info.huff_table_dc_chrom, jpeg_default_huff_dc_chrom, JPEG_HUFF_TABLE_DC_SIZE)!=0)
            || (memcmp(jpeg_info->header_info.huff_table_ac_lum, jpeg_default_huff_ac_lum, JPEG_HUFF_TABLE_AC_SIZE)!=0)
            || (memcmp(jpeg_info->header_info.huff_table_ac_chrom, jpeg_default_huff_ac_chrom, JPEG_HUFF_TABLE_AC_SIZE)!=0)){
            jpeg_info->header_info.huff_is_default = 0;
            printf("jpeg decoder: is not default huff table!! \n");
            int i = 0;
            int tb_idx = 0;
            int data_flag = 0, zero_flag = 0;
            uint8_t ptrCnt = 0;
            uint16_t huffCode = 0;
            for (tb_idx = 0; tb_idx < 4; tb_idx ++) {
                uint8_t *table_buf = jpeg_info->header_info.huff_table_dc_lum;
                data_flag = 0;
                zero_flag = 0;
                huffCode = 0;
                ptrCnt = 0;

                if (tb_idx==1)
                    table_buf = jpeg_info->header_info.huff_table_dc_chrom;
                else if (tb_idx==2)
                    table_buf = jpeg_info->header_info.huff_table_ac_lum;
                else if (tb_idx==3)
                    table_buf = jpeg_info->header_info.huff_table_ac_chrom;

                for (i = (tb_idx*16); i<(tb_idx*16+16); i++) {
                    if (table_buf[i-tb_idx*16]) {
                        jpeg_info->huff_table_ptr[i] = ptrCnt;
                        ptrCnt += table_buf[i-tb_idx*16];
                        jpeg_info->huff_table_min[i] = huffCode;
                        jpeg_info->huff_table_max[i] = huffCode + (table_buf[i-tb_idx*16] - 1);
                        data_flag = 1;
                        zero_flag = 0;
                    } else {
                        jpeg_info->huff_table_ptr[i] = 0xFF;
                        jpeg_info->huff_table_min[i] = 0xFFFF;
                        jpeg_info->huff_table_max[i] = 0xFFFF;
                        zero_flag = 1;
                    }
                    //printf("%x : ptr: %x, min: %x, max: %x\n", i+1, jpeg_info->huff_table_ptr[i], jpeg_info->huff_table_min[i], jpeg_info->huff_table_max[i]);
                    if (data_flag == 1) {
                        if (zero_flag == 1)
                            huffCode <<= 1;
                        else
                            huffCode = (jpeg_info->huff_table_max[i] + 1) << 1;
                    }
                }
            }
        }
    }

    return CRANE_JPEG_DECODE_PASS;
}

static uint32_t jpu_decode_deinit(void)
{
    if (jpu_dev.jpu_open_cnt == 0) {
        return 0;
    }

    jpu_irq_disable();
    media_clk_jpu_off();

    if(jpeg_decode_flag != MAX_FLAG_NUM) {
        uos_delete_flag(jpeg_decode_flag);
        jpeg_decode_flag = MAX_FLAG_NUM;
    } else {
        jpeg_decode_reg = 0;
    }

    jpu_dev.state = JPU_STATE_OFF;
    jpu_dev.jpu_open_cnt--;

    return 0;
}

static void jpu_decode_init(void)
{
    uint32_t val = 0;

    jpu_common_init();

    if (jpu_dev.jpu_open_cnt == 0) {
        jpu_dev.state = JPU_STATE_DEC_ACTIVE;
        jpu_dev.jpu_open_cnt++;

        jpeg_decode_flag = uos_create_flag();

        media_clk_jpu_on();
        jpu_clk_reset();
        jpu_irq_enable();
        writel(0, 0xD420C2B0);

        jpu_write(MJPEG_PIC_START_REG, BIT_1);   // BIT_1 : initialize decode/encode status
        do {
            val = (BIT_1 & jpu_read(MJPEG_PIC_START_REG));
        } while (val == BIT_1);
    } else {
        printf("JPU is busy!\n");
    }
}

static void jpu_decode_bbc_ready(uint32_t page_ptr, uint32_t src_buffer)
{
    /* Copy 256*2 stream buffer to gram first*/
    uint32_t busy_reg = 1;

    jpu_write(MJPEG_BBC_CUR_POS_REG, page_ptr);
    jpu_write(MJPEG_BBC_EXT_ADDR_REG, src_buffer + (page_ptr << 8));
    jpu_write(MJPEG_BBC_INT_ADDR_REG, (page_ptr & 1) << 6);
    jpu_write(MJPEG_BBC_DATA_CNT_REG, 256 / 4);
    jpu_write(MJPEG_BBC_COMMAND_REG, 0);
    while (busy_reg == 1) {
        busy_reg = jpu_read(MJPEG_BBC_BUSY_REG);
    }

    busy_reg = 1;
    page_ptr ++;
    jpu_write(MJPEG_BBC_CUR_POS_REG, page_ptr);
    jpu_write(MJPEG_BBC_EXT_ADDR_REG, src_buffer + (page_ptr << 8));
    jpu_write(MJPEG_BBC_INT_ADDR_REG, (page_ptr & 1) << 6);
    jpu_write(MJPEG_BBC_DATA_CNT_REG, 256 / 4);
    jpu_write(MJPEG_BBC_COMMAND_REG, 0);
    while (busy_reg == 1) {
        busy_reg = jpu_read(MJPEG_BBC_BUSY_REG);
    }

    page_ptr ++;
    jpu_write(MJPEG_BBC_CUR_POS_REG, page_ptr);
}

static void jpu_decode_seq_init(jpu_decoder_info_t *jpeg_info)
{
    uint32_t val = 0;

    jpu_write(MJPEG_GBU_WD_PTR_REG, jpeg_info->word_ptr);
    jpu_write(MJPEG_GBU_BBSR_REG, 0);
    jpu_write(MJPEG_GBU_BBER_REG, 256 / 4 * 2 - 1);

    if (jpeg_info->page_ptr & 1) {
        jpu_write(MJPEG_GBU_BBIR_REG, 0);
        jpu_write(MJPEG_GBU_BBHR_REG, 0);
    } else {
        jpu_write(MJPEG_GBU_BBIR_REG, 256 / 4);
        jpu_write(MJPEG_GBU_BBHR_REG, 256 / 4);
    }

    // if set to 1, BBC block is working.
    jpu_write(MJPEG_BBC_CTRL_REG, ((CRANE_ENDIAN_VALUE & 0x3) << 1) + BIT_0);   // BIT_0: BBC auto run
    jpu_write(MJPEG_BBC_BAS_ADDR_REG, jpeg_info->src_addr);

    jpu_write(MJPEG_GBU_CTRL_REG, BIT_2);         //bit initializes GBU in decoder case

    jpu_write(MJPEG_BBC_END_ADDR_REG, jpeg_info->src_addr + jpeg_info->buffer_size);
    jpu_write(MJPEG_BBC_RD_PTR_REG, jpeg_info->src_addr);

    val = ((jpeg_info->huff_ac_idx & 0x7) << 10);
    val += ((jpeg_info->huff_dc_idx & 0x7) << 7);
    val += ((jpeg_info->header_info.huff_is_default?0:0x1) << 6);
    val += ((0x1 << 2) + (0 & 0x3));  // normal mode
    jpu_write(MJPEG_PIC_CTRL_REG, val);

    val = ((jpeg_info->header_info.width & 0xFFFF) << 16) + (jpeg_info->header_info.height & 0xFFFF);
    jpu_write(MJPEG_PIC_SIZE_REG, val);

    if ((jpu_decode_config.mirror_mode == 0) && (jpu_decode_config.rotate_mode == 0)) {
        jpu_write(MJPEG_ROT_INFO_REG, 0x0);
    } else {
        jpu_write(MJPEG_ROT_INFO_REG, (0x1 << 4) + ((jpu_decode_config.mirror_mode & 0x3) << 2) + (jpu_decode_config.rotate_mode & 0x3));
    }

    uint32_t slice_num = 32;
    uint32_t comp_num = 0, blk_num = 0;
    uint32_t comp_info0 = 0;
    uint32_t buf_num = 0x4;
    uint32_t req_num = 0x4;
    uint32_t comp_info1 = 0x5;   //4'b0101
    uint32_t comp_info2 = 0x5;   //4'b0101

    switch (jpeg_info->yuv_format) {
        case 0:   // FORMAT_420
            blk_num = 6;
            comp_num = 3;
            comp_info0 = 0xa;   //4'b1010
            if (req_num >= 2){
                req_num = 2;
            }
            break;
        default:
            printf("jpeg decode not support this format yet: %d\n", jpeg_info->yuv_format);
            break;
    }
    val = ((slice_num & 0xFFFF) << 16) + ((buf_num & 0x7) << 3) + (req_num & 0x7);
    jpu_write(MJPEG_OP_INFO_REG, val);
    // 4:2:0 format - 0x0006_3A55
    val = ((blk_num & 0xF) << 16) + ((comp_num & 0x7) << 12) + ((comp_info0 & 0xF) << 8) + ((comp_info1 & 0xF) << 4) + (comp_info2 & 0xF);
    jpu_write(MJPEG_MCU_INFO_REG, val);

    jpu_write(MJPEG_SCL_INFO_REG, 0x0);   // No scaling
    // [5:2] - pack mode
    // - 4'b0000: pack mode disable
    // [1:0] - dpb uv interleave mode
    // - 2'b10: CbCr interleave (e.g. NV12 in 4:2:0 or NV16 in 4:2:2)
    jpu_write(MJPEG_DPB_CONFIG_REG, ((0 & 0x3) << 6) + ((0 & 0xF) << 2) + (1 << 1) + 0x0);

    jpu_write(MJPEG_RST_INTVAL_REG, 0);
}

static void jpu_decode_dqt_set(jpu_decoder_info_t *jpeg_info)
{
    unsigned char *fpQTable = NULL;
    int i;

    // Comp. 0
    fpQTable = jpeg_info->header_info.qtable[0];
    jpu_write(MJPEG_QMAT_CTRL_REG, 0x3);
    for (i = 0; i < 64; i = i + 1)
        jpu_write(MJPEG_QMAT_DATA_REG, fpQTable[i]);
    jpu_write(MJPEG_QMAT_CTRL_REG, 0x0);

    // Comp. 1
    if(jpeg_info->header_info.qtable_got & 0x2) {
        fpQTable = jpeg_info->header_info.qtable[1];
    }
    jpu_write(MJPEG_QMAT_CTRL_REG, 0x43);
    for (i =0; i < 64; i = i + 1)
        jpu_write(MJPEG_QMAT_DATA_REG, fpQTable[i]);
    jpu_write(MJPEG_QMAT_CTRL_REG, 0x0);

    // Comp. 2
    if(jpeg_info->header_info.qtable_got & 0x4) {
        fpQTable = jpeg_info->header_info.qtable[2];
    }
    jpu_write(MJPEG_QMAT_CTRL_REG, 0x83);
    for (i = 0; i < 64; i = i + 1)
        jpu_write(MJPEG_QMAT_DATA_REG, fpQTable[i]);
    jpu_write(MJPEG_QMAT_CTRL_REG, 0x0);
}

static void jpu_decode_dht_set(jpu_decoder_info_t *jpeg_info)
{
    int i = 0;
    uint32_t reg = 0;
    // MIN Table
    jpu_write(MJPEG_HUFF_CTRL_REG, 0x3);
    for (i=0; i<64; i++) {
        reg = jpeg_info->huff_table_min[i];
        jpu_write(MJPEG_HUFF_DATA_REG, reg);
    }

    // MAX Table
    jpu_write(MJPEG_HUFF_CTRL_REG, 0x403);
    jpu_write(MJPEG_HUFF_ADDR_REG, 0x440);
    for (i=0; i<64; i++){
        reg = jpeg_info->huff_table_max[i];
        jpu_write(MJPEG_HUFF_DATA_REG, reg);
    }

    // PTR Table
    jpu_write(MJPEG_HUFF_CTRL_REG, 0x803);
    jpu_write(MJPEG_HUFF_ADDR_REG, 0x880);
    for (i=0; i<64; i++){
        reg = jpeg_info->huff_table_ptr[i];
        jpu_write(MJPEG_HUFF_DATA_REG, reg);
    }

    // VAL Table
    jpu_write(MJPEG_HUFF_CTRL_REG, 0xC03);
    // VAL DC Lum.
    for (i=0; i<JPEG_HUFF_TABLE_DC_VALUE_SIZE; i++) {
        if(i<jpeg_info->header_info.huff_table_dc_lum_length) {
            reg = jpeg_info->header_info.huff_table_dc_lum[JPEG_HUFF_TABLE_LENGTH_SIZE + i];
            jpu_write(MJPEG_HUFF_DATA_REG, reg);
        } else {
            jpu_write(MJPEG_HUFF_DATA_REG, 0xffffffff);
        }
    }
    // VAL DC Chrom.
    for (i=0; i<JPEG_HUFF_TABLE_DC_VALUE_SIZE; i++) {
        if(i<jpeg_info->header_info.huff_table_dc_chrom_length) {
            reg = jpeg_info->header_info.huff_table_dc_chrom[JPEG_HUFF_TABLE_LENGTH_SIZE + i];
            jpu_write(MJPEG_HUFF_DATA_REG, reg);
        } else {
            jpu_write(MJPEG_HUFF_DATA_REG, 0xffffffff);
        }
    }
    // VAL AC Lum.
    for (i=0; i<JPEG_HUFF_TABLE_AC_VALUE_SIZE; i++) {
        if(i<jpeg_info->header_info.huff_table_ac_lum_length) {
            reg = jpeg_info->header_info.huff_table_ac_lum[JPEG_HUFF_TABLE_LENGTH_SIZE + i];
            jpu_write(MJPEG_HUFF_DATA_REG, reg);
        } else {
            jpu_write(MJPEG_HUFF_DATA_REG, 0xffffffff);
        }
    }
    // VAL AC Chrom.
    for (i=0; i<JPEG_HUFF_TABLE_AC_VALUE_SIZE; i++) {
        if(i<jpeg_info->header_info.huff_table_ac_chrom_length) {
            reg = jpeg_info->header_info.huff_table_ac_chrom[JPEG_HUFF_TABLE_LENGTH_SIZE + i];
            jpu_write(MJPEG_HUFF_DATA_REG, reg);
        } else {
            jpu_write(MJPEG_HUFF_DATA_REG, 0xffffffff);
        }
    }

    // end SerPeriHuffTab
    jpu_write(MJPEG_HUFF_CTRL_REG, 0);
}


static uint8_t jpu_decode_run(jpu_decoder_info_t *jpeg_info)
{
    // InitPic
    jpu_write(MJPEG_RST_INDEX_REG, 0x0);
    jpu_write(MJPEG_RST_COUNT_REG, 0x0);

    jpu_write(MJPEG_DPCM_DIFF_Y_REG, 0x0);
    jpu_write(MJPEG_DPCM_DIFF_CB_REG, 0x0);
    jpu_write(MJPEG_DPCM_DIFF_CR_REG, 0x0);

    jpu_write(MJPEG_GBU_FF_RPTR_REG, jpeg_info->read_ptr);
    jpu_write(MJPEG_GBU_CTRL_REG, 0x3);

    jpu_write(MJPEG_DPB_BASE00_REG, jpeg_info->out_y_addr);
    jpu_write(MJPEG_DPB_BASE01_REG, jpeg_info->out_u_addr);
    jpu_write(MJPEG_DPB_BASE02_REG, jpeg_info->out_v_addr);

    jpu_write(MJPEG_DPB_YSTRIDE_REG, jpeg_info->header_info.width);
    jpu_write(MJPEG_DPB_CSTRIDE_REG, jpeg_info->header_info.width);

    jpu_write(MJPEG_PIC_START_REG, BIT_0);  // Start decode process

    if(jpeg_decode_flag != MAX_FLAG_NUM) {
        uint32_t actual_flags = 0;
        int rc = 0;

        rc = uos_wait_flag(jpeg_decode_flag, MJPEG_HW_IRQ_STATUS_CODEC_DONE | MJPEG_HW_IRQ_STATUS_DECODE_ERROR,
                    UOS_FLAG_OR_CLEAR, &actual_flags, JPU_TIMEOUT);
        if (0 == rc) {
            if(actual_flags & MJPEG_HW_IRQ_STATUS_CODEC_DONE) {
                return CRANE_JPEG_DECODE_PASS;
            } else if(actual_flags & MJPEG_HW_IRQ_STATUS_DECODE_ERROR) {
                printf("Failed: jpu hardware decoding error!!!\n");
                return CRANE_JPEG_DECODE_FAIL;
            } else {
                printf("Failed: jpu hardware decoding other error!!!\n");
                return CRANE_JPEG_DECODE_FAIL;
            }
        } else {
            printf("Failed: jpu hardware decoding timeout!!!!!\n");
            return CRANE_JPEG_DECODE_FAIL;
        }
    }else {
        while(1) {
            if(jpeg_decode_reg & MJPEG_HW_IRQ_STATUS_CONFIG_DONE) {
                return CRANE_JPEG_DECODE_PASS;
            } else if(jpeg_decode_reg & MJPEG_HW_IRQ_STATUS_DECODE_ERROR) {
                printf("Failed: jpu hardware decoding error!!!\n");
                return CRANE_JPEG_DECODE_FAIL;
            }
            mdelay(1);
        }
    }
}

#endif
